﻿using Devonline.AspNetCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;

namespace Devonline.Identity;

public static class IdentityExtensions
{
    /// <summary>
    /// 针对字符串类型主键的 IIdentity 实例获取对应的 IdentityType
    /// </summary>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType(this IIdentity<string> identity) => identity.GetIdentityType<string>();

    /// <summary>
    /// 针对 IIdentity<TKey> 实例获取对应的 IdentityType
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType<TKey>(this IIdentity<TKey> identity) where TKey : IConvertible, IEquatable<TKey> => identity.GetIdentityType<IIdentity<TKey>, TKey>();

    /// <summary>
    /// 针对任意引用类型获取对应的身份 IdentityType
    /// </summary>
    /// <typeparam name="TIdentity"></typeparam>
    /// <param name="identity"></param>
    /// <returns></returns>
    public static IdentityType GetIdentityType<TIdentity, TKey>(this TIdentity identity) where TIdentity : class, IIdentity<TKey> where TKey : IConvertible, IEquatable<TKey> => identity switch
    {
        User<TKey> => IdentityType.User,
        Role<TKey> => IdentityType.Role,
        Group<TKey> => IdentityType.Group,
        Level<TKey> => IdentityType.Level,
        _ => IdentityType.System
    };

    /// <summary>
    /// 根据身份类型和编号获取对应的用户
    /// </summary>
    /// <typeparam name="TKey">主键类型</typeparam>
    /// <param name="context">身份数据库上下文</param>
    /// <param name="identityType">身份分配类型</param>
    /// <param name="identities">身份编号</param>
    /// <returns></returns>
    public static async Task<List<User<TKey>>> GetIdentityUsers<TKey>(this IdentityDbContext<TKey> context, IdentityType identityType = IdentityType.User, params TKey[] identities) where TKey : IConvertible, IEquatable<TKey>
    {
        if (!identities.IsNotNullOrEmpty())
        {
            return null;
        }

        return identityType switch
        {
            IdentityType.User => await context.Users.Where(x => x.State == DataState.Available && identities.Contains(x.Id)).ToListAsync(),
            IdentityType.Role => await context.Users.Where(x => x.State == DataState.Available && context.UserRoles.Where(a => identities.Contains(a.RoleId)).Select(a => a.UserId).Contains(x.Id)).ToListAsync(),
            IdentityType.Group => await context.Users.Where(x => x.State == DataState.Available && context.UserGroups.Where(a => identities.Contains(a.GroupId)).Select(a => a.UserId).Contains(x.Id)).ToListAsync(),
            IdentityType.Level => await context.Users.Where(x => x.State == DataState.Available && x.LevelId != null && identities.Contains(x.LevelId)).ToListAsync(),
            IdentityType.System => await context.Users.Where(x => x.State == DataState.Available && x.Type == AuthorizeType.System && x.UserName == AppSettings.USER_SYSTEM).ToListAsync(),
            IdentityType.Anonymous => await context.Users.Where(x => x.State == DataState.Available && x.Type == AuthorizeType.Anonymous && x.UserName == AppSettings.USER_ANONYMOUS).ToListAsync(),
            _ => await context.Users.Where(x => x.State == DataState.Available).ToListAsync(),
        };
    }

    /// <summary>
    /// 根据身份类型和编号获取对应的用户
    /// </summary>
    /// <param name="context">身份数据库上下文</param>
    /// <param name="identityType">身份分配类型</param>
    /// <param name="identities">身份编号</param>
    public static async Task<List<User>> GetIdentityUsers(this IdentityDbContext context, IdentityType identityType = IdentityType.User, params string[] identities)
    {
        if (!identities.IsNotNullOrEmpty())
        {
            return null;
        }

        return identityType switch
        {
            IdentityType.User => await context.Users.Where(x => x.State == DataState.Available && identities.Contains(x.Id) || identities.Contains(x.UserName)).ToListAsync(),
            IdentityType.Role => await context.Users.Where(x => x.State == DataState.Available && context.UserRoles.Where(a => identities.Contains(a.RoleId)).Select(a => a.UserId).Contains(x.Id)).ToListAsync(),
            IdentityType.Group => await context.Users.Where(x => x.State == DataState.Available && context.UserGroups.Where(a => identities.Contains(a.GroupId)).Select(a => a.UserId).Contains(x.Id)).ToListAsync(),
            IdentityType.Level => await context.Users.Where(x => x.State == DataState.Available && x.LevelId != null && identities.Contains(x.LevelId)).ToListAsync(),
            IdentityType.System => await context.Users.Where(x => x.State == DataState.Available && x.Type == AuthorizeType.System && x.UserName == AppSettings.USER_SYSTEM).ToListAsync(),
            IdentityType.Anonymous => await context.Users.Where(x => x.State == DataState.Available && x.Type == AuthorizeType.Anonymous && x.UserName == AppSettings.USER_ANONYMOUS).ToListAsync(),
            _ => await context.Users.Where(x => x.State == DataState.Available).ToListAsync(),
        };
    }

    /// <summary>
    /// 获取用户所有已分配的身份信息编号
    /// </summary>
    /// <param name="context">数据库上下文</param>
    /// <param name="user">当前用户</param>
    /// <returns></returns>
    public static async Task<List<string>> GetUserIdentities(this IdentityDbContext context, User user)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(user);
        ArgumentNullException.ThrowIfNull(user.Id);

        var identities = new List<string> { user.Id };

        //1. 获取用户角色身份
        var roleIds = await context.UserRoles.Where(x => x.UserId.Equals(user.Id)).Select(x => x.RoleId).Distinct().ToListAsync();
        if (roleIds.IsNotNullOrEmpty())
        {
            identities.AddRange(roleIds);
        }

        //2. 获取用户组织身份
        var groupIds = await context.UserGroups.Where(x => x.UserId.Equals(user.Id)).Select(x => x.GroupId).Distinct().ToListAsync();
        if (groupIds.IsNotNullOrEmpty())
        {
            identities.AddRange(groupIds);
            foreach (var groupId in groupIds)
            {
                var groups = await context.GetParentsAsync<Group>(groupId);
                if (groups.IsNotNullOrEmpty())
                {
                    identities.AddRange(groups.Select(x => x.Id));
                }
            }
        }

        //3. 获取用户级别身份
        if (!string.IsNullOrWhiteSpace(user.LevelId))
        {
            var level = await context.Levels.FindAsync(user.LevelId);
            if (level != null)
            {
                identities.Add(level.Id);
            }
        }

        //4. 获取用户系统身份
        if (user.Type == AuthorizeType.System)
        {
            //系统中只能有一个 System 类型的用户的用户名叫: AppSettings.USER_SYSTEM 的
            var systemUser = await context.Users.FirstOrDefaultAsync(x => x.Type == AuthorizeType.System && x.UserName == AppSettings.USER_SYSTEM);
            if (systemUser != null)
            {
                identities.Add(systemUser.Id);
            }
        }

        //5. 获取匿名用户身份
        //系统中只能有一个 Anonymous 类型的用户, 且用户名叫: AppSettings.USER_ANONYMOUS 的
        var anonymousUser = await context.Users.FirstOrDefaultAsync(x => x.Type == AuthorizeType.Anonymous && x.UserName == AppSettings.USER_ANONYMOUS);
        if (anonymousUser != null)
        {
            identities.Add(anonymousUser.Id);
        }

        return identities;
    }

    /// <summary>
    /// 获取用户所有已分配身份可获取的资源列表及其树形结构
    /// TODO TBC 现阶段先不判断 Condition 条件
    /// </summary>
    /// <param name="context">数据库上下文</param>
    /// <param name="cache">分布式缓存</param>
    /// <param name="userName">用户名</param>
    /// <param name="force">是否强制获取</param>
    /// <returns></returns>
    public static async Task<UserInfoViewModel> GetUserAccessResourcesAsync(this IdentityDbContext context, IDistributedCache cache, string userName, bool force = false)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(cache);
        ArgumentNullException.ThrowIfNull(userName);

        var cacheKey = AppSettings.CACHE_USER + userName;
        UserInfoViewModel userInfo = null;
        if (!force)
        {
            userInfo = cache.GetValue<UserInfoViewModel>(cacheKey);
        }

        if (force || userInfo == null)
        {
            var user = await context.Users.FirstOrDefaultAsync(x => x.UserName == userName);
            user.PasswordHash = null;
            var identities = await context.GetUserIdentities(user);
            var queryable = context.AccessRules.Where(x => x.State == DataState.Available && (!x.ExpireTime.HasValue || x.ExpireTime.Value >= DateTime.Now) && (x.LimitAccessCount == 0 || x.AccessCount <= x.LimitAccessCount));
            if (identities.IsNotNullOrEmpty())
            {
                queryable = queryable.Where(x => identities.Contains(x.IdentityId) || x.IdentityType == IdentityType.All);
            }

            var accessRules = (await queryable.ToListAsync()).CopyTo<List<AccessRuleViewModel>>();
            var (resources, resourcesTree) = await context.GetFullResources(cache, force);
            userInfo = new UserInfoViewModel { User = user.CopyTo<UserViewModel>(), Resources = new List<ResourceViewModel>(), ResourceTree = resourcesTree };
            foreach (var resource in resourcesTree)
            {
                userInfo.Resources.AddRange(GetAccessResources(resource, new List<AccessRuleViewModel>(), accessRules));
                CleanResourceTree(resource);
            }

            resourcesTree.RemoveWhere(x => !x.HasPermission && (!x.Children.Any()));
            cache.SetValue(cacheKey, userInfo);
        }

        return userInfo;
    }

    /// <summary>
    /// 获取全部的资源列表及其树形结构
    /// </summary>
    /// <param name="context">数据库上下文</param>
    /// <param name="cache">分布式缓存</param>
    /// <param name="force">是否强制获取</param>
    /// <returns></returns>
    public static async Task<(List<ResourceViewModel> resources, List<ResourceViewModel> resourcesTree)> GetFullResources(this IdentityDbContext context, IDistributedCache cache, bool force = false)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(cache);

        List<ResourceViewModel> resources = null;
        List<ResourceViewModel> resourcesTree = null;
        var cacheKey = AppSettings.CACHE_APPLICATION + "RESOURCES";
        var treeCacheKey = AppSettings.CACHE_APPLICATION + "RESOURCES_TREE";
        if (!force)
        {
            resources = cache.GetValue<List<ResourceViewModel>>(cacheKey);
            resourcesTree = cache.GetValue<List<ResourceViewModel>>(treeCacheKey);
        }

        if (force || resources == null || resourcesTree == null)
        {
            resources = (await context.Resources.Where(x => x.State == DataState.Available).ToListAsync()).CopyTo<List<ResourceViewModel>>();
            cache.SetValue(cacheKey, resources);

            resourcesTree = resources.Where(x => x.ParentId == null).ToList();
            foreach (var resource in resourcesTree)
            {
                GetChildrenResources(resource, resources);
            }

            cache.SetValue(treeCacheKey, resourcesTree);
        }

        return (resources, resourcesTree);
    }

    /// <summary>
    /// 返回用户是否有某个资源的访问权限, 判断数据来自缓存, 判断依据为资源编号或资源内容必须包含在用户可访问资源缓存列表中
    /// </summary>
    /// <param name="context"></param>
    /// <param name="cache"></param>
    /// <param name="userName"></param>
    /// <param name="resource"></param>
    /// <returns></returns>
    public static async Task<bool> UserHasPermissionAsync(this IdentityDbContext context, IDistributedCache cache, string userName, string resource)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(cache);
        ArgumentNullException.ThrowIfNull(userName);
        ArgumentNullException.ThrowIfNull(resource);

        var cacheKey = AppSettings.CACHE_USER + userName;
        var userInfo = await context.GetUserAccessResourcesAsync(cache, userName);
        return userInfo.Resources.Any(x => x.Id == resource || x.Content == resource);
    }

    /// <summary>
    /// 递归获取并设置资源的子资源
    /// </summary>
    /// <param name="resource"></param>
    /// <param name="resources"></param>
    private static void GetChildrenResources(ResourceViewModel resource, List<ResourceViewModel> resources)
    {
        resource.Children = resources.Where(x => x.ParentId == resource.Id).ToList();
        if (resource.Children.IsNotNullOrEmpty())
        {
            foreach (var child in resource.Children)
            {
                GetChildrenResources(child, resources);
            }
        }
    }
    /// <summary>
    /// 对权限树中的当前节点及其子节点设置权限
    /// </summary>
    /// <param name="resource">当前资源节点</param>
    /// <param name="accessRules">当前节点拥有的访问规则列表</param>
    /// <param name="fullAccessRules">当前用户获取到的所有访问规则列表</param>
    private static List<ResourceViewModel> GetAccessResources(ResourceViewModel resource, List<AccessRuleViewModel> accessRules, List<AccessRuleViewModel> fullAccessRules)
    {
        var resources = new List<ResourceViewModel>();

        //1. 先获取当前资源满足条件的访问规则
        var currentAccessRules = fullAccessRules.Where(x => x.ResourceId == resource.Id).UnionBy(accessRules, x => x.Id).OrderByDescending(x => x.Priority).ToList();

        //2. 按照规则优先级最高的一条设置当前资源的权限, 如果当前资源没有访问规则，则视为无权限
        resource.HasPermission = currentAccessRules.FirstOrDefault()?.IsAllow == AllowType.Allow;
        if (resource.HasPermission)
        {
            resources.Add(new ResourceViewModel
            {
                Id = resource.Id,
                State = resource.State,
                Name = resource.Name,
                Alias = resource.Alias,
                Type = resource.Type,
                Image = resource.Image,
                Title = resource.Title,
                Content = resource.Content,
                ParentId = resource.ParentId,
                Description = resource.Description,
                ResourceType = resource.ResourceType,
                IdentityType = resource.IdentityType,
                OwnerId = resource.OwnerId,
                LevelId = resource.LevelId,
                AccessLevel = resource.AccessLevel,
                HasPermission = resource.HasPermission,
                //TODO 记录当前资源的访问规则会增加系统处理量, 但使用率不高
                //AccessRules = currentAccessRules
            });
        }

        //如果当前资源有子资源
        if (resource.Children.IsNotNullOrEmpty())
        {
            //3. 且当前资源有权访问, 则递归设置子资源的权限
            foreach (var child in resource.Children)
            {
                resources.AddRange(GetAccessResources(child, currentAccessRules, fullAccessRules));
            }
        }

        return resources;
    }
    /// <summary>
    /// 对用户可访问的资源树进行剪枝操作
    /// </summary>
    /// <param name="resource"></param>
    /// <returns></returns>
    private static bool CleanResourceTree(ResourceViewModel resource)
    {
        var result = resource.HasPermission;
        if (resource.Children.IsNotNullOrEmpty())
        {
            foreach (var child in resource.Children)
            {
                if (CleanResourceTree(child))
                {
                    result = true;
                    break;
                }
            }

            if (!result)
            {
                resource.Children.Clear();
            }

            resource.Children.RemoveWhere(x => !x.HasPermission && (!x.Children.Any()));
        }

        return result;
    }
}